package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"time"

	"github.com/gin-contrib/multitemplate"
	"github.com/gin-gonic/gin"
	"golang.org/x/sync/errgroup"
)

var (
	g errgroup.Group
)

type Login struct {
	User     string `form:"user" json:"user" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

func main() {
	/***
	a. 下面的方法因为开启的端口不同，需要一个个开启调用
	b. 也可以同时开启多个服务
	*/

	// 1. gin基础
	// run()

	// 2. 模板继承
	// moduleRun()

	// 3. 获取querystring参数
	// getQueryString()

	// 4. 获取form参数
	// getForm()

	// 5. 获取json参数
	// getJson()

	// 6. 获取path参数, 如/user/search/小王子/沙河
	// getPath()

	// 7. 参数绑定
	// paramsBind()

	// 8.1 单文件上传
	// upload()

	// 8.2 多文件上传
	// upload_files()

	// 9. 重定向
	// redict()

	// 10. gin路由: 单服务使用仍然和上面的代码一致
	// myGinView()

	// 11. 路由组: 单服务使用仍然和上面的代码一致
	// ginRouteGroup()

	// 12. 中间件: 单服务使用仍然和上面的代码一致
	// middleware()

	/**
	13. bind绑定参数(把前端传递的参数与结构体进行绑定): 需要给结构体加tag: json xml yaml form uri...
	MustBind: 不用，校验失败会修改状态码
	ShouldBind: 如果校验失败会返回错误
		ShouldBind(formdat、form-urlencoded)
		ShouldBindJson
		ShouldBindQuery
		ShouldBindUri
	*/
	// ShouldBindJson()
	// ShouldBindQuery()
	// ShouldBindUri()
	// ShouldBindOther()

	// 14. bind绑定器: 需要加binding tag
	// ValidCommonUse()

	// 15. gin自带日志模块
	// GinSelfLog()

	// finally. 开启多个服务并行(以10和11、12举例)
	server01 := &http.Server{
		Addr:         ":8088",
		Handler:      myGinView(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
	server02 := &http.Server{
		Addr:         ":8089",
		Handler:      ginRouteGroup(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
	server03 := &http.Server{
		Addr:         ":8090",
		Handler:      middleware(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	// 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务
	g.Go(func() error {
		return server01.ListenAndServe()
	})

	g.Go(func() error {
		return server02.ListenAndServe()
	})

	g.Go(func() error {
		return server03.ListenAndServe()
	})

	if err := g.Wait(); err != nil {
		log.Fatal(err)
	}
}

// 1.1 渲染tmpl模板
func loadTemplates(templateDir string) multitemplate.Renderer {
	r := multitemplate.NewRenderer()
	layouts, err := filepath.Glob(templateDir + "/layouts/*.tmpl")
	if err != nil {
		panic(err.Error())
	}
	includes, err := filepath.Glob(templateDir + "/includes/*.tmpl")
	if err != nil {
		panic(err.Error())
	}
	// 为layouts/和includes/目录生成 templates map
	for _, include := range includes {
		layoutCopy := make([]string, len(layouts))
		copy(layoutCopy, layouts)
		files := append(layoutCopy, include)
		r.AddFromFiles(filepath.Base(include), files...)
	}
	return r
}

// 2 模板继承
func indexFunc(c *gin.Context) {
	var msg struct {
		Name string
		Age  int8
		Sex  string
	}
	msg.Name = "小王子"
	msg.Age = 18
	msg.Sex = "男"
	c.HTML(http.StatusOK, "index.tmpl", msg)
}

func homeFunc(c *gin.Context) {
	c.HTML(http.StatusOK, "home.tmpl", nil)
}

func moduleRun() {
	r := gin.Default()
	r.HTMLRender = loadTemplates("../../templates")
	r.GET("/index", indexFunc)
	r.GET("/home", homeFunc)
	r.Run(":8081")
}

// 3.1 各种返回的渲染
func returnSomething() {
	router := gin.Default()
	// a. json
	router.GET("/someJson", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"code": 200,
		})
	})

	// b. struct
	router.GET("/someStruct", func(c *gin.Context) {
		var Stud struct {
			Name string
			Age  int
		}
		Stud.Name = "猴子"
		Stud.Age = 500
		c.JSON(http.StatusOK, Stud)
	})

	// c. xml
	router.GET("/someXml", func(c *gin.Context) {
		c.XML(http.StatusOK, gin.H{"message": "xml content"})
	})

	// d. yaml
	router.GET("/someYaml", func(c *gin.Context) {
		c.YAML(http.StatusOK, gin.H{"message": "yaml content"})
	})

	// e. protobuf: 谷歌公司开发的高效存储工具: * 需要build运行 *
	// router.GET("/someProtobuf", func(c *gin.Context) {
	// 	resps := []int64{int64(1), int64(2), int64(3)}
	// 	label := "label"
	// 	// 传protobuf格式数据
	// 	data := &protoexample.Test{
	// 		Label: label,
	// 		Resps: resps,
	// 	}
	// 	c.ProtoBuf(http.StatusOK, data)
	// })
}

// 3.2 DefaultQuery
func getQueryString() {
	r := gin.Default()
	r.GET("/query", func(c *gin.Context) {
		// http://127.0.0.1:8082/query?username=小王子&address=上海市
		username := c.DefaultQuery("username", "小王子")
		address := c.Query("address")
		// 输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})
	r.Run(":8082")
}

// 4. postForm
func getForm() {
	r := gin.Default()
	r.POST("/form", func(c *gin.Context) {
		// *DefaultPostForm* 取不到值时会返回指定的默认值
		// username := c.DefaultPostForm("username", "小王子")
		username := c.PostForm("username")
		address := c.PostForm("address")
		// 多选框，PostFormArray接收为一个数组
		hobbyArray := c.PostFormArray("hobby")
		// 输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
			"hobby":    hobbyArray,
		})
	})
	r.Run(":8083")
}

// 5. json参数
func getJson() {
	r := gin.Default()
	r.POST("/json", func(c *gin.Context) {
		// 注意：下面为了举例子方便，暂时忽略了错误处理
		b, _ := c.GetRawData() // 从c.Request.Body读取请求数据
		// 定义map或结构体
		var m map[string]interface{}
		// 反序列化
		_ = json.Unmarshal(b, &m)

		c.JSON(http.StatusOK, m)
	})
}

// 6.1 param
func getPath() {
	r := gin.Default()
	// http://127.0.0.1:8084/user/search/小王子/上海
	r.GET("/user/search/:username/:address", func(c *gin.Context) {
		username := c.Param("username")
		address := c.Param("address")
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})
	r.Run(":8084")
}

// 6.2 param *action
func getPathAction() {
	r := gin.Default()
	r.GET("/user/:username/*action", func(ctx *gin.Context) {
		username := ctx.Param("username")
		action := ctx.Param("action")
		ctx.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"action":   action,
		})
	})
	r.Run("80001")
	/*
		访问http://127.0.0.1:8084/user/小王子/bind/something
		输出：
			{
				"message":  "ok",
				"username": "小王子",
				"action":   "/bind/something",
			}
	*/
}

// 7. json/form/querystring bind(绑定)
func paramsBind() {
	router := gin.Default()

	// a. 绑定JSON的示例 ({"user": "q1mi", "password": "123456"})
	router.POST("/loginJSON", func(c *gin.Context) {
		var login Login

		if err := c.ShouldBindJSON(&login); err == nil {
			fmt.Printf("login info: %#v\n", login)
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		}
	})

	// b. 绑定form表单示例 (user=q1mi&password=123456)
	router.POST("/loginForm1", func(c *gin.Context) {
		var login Login
		// ShouldBind()会根据请求的Content-Type自行选择绑定器
		if err := c.ShouldBind(&login); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// c. 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)
	router.GET("/loginForm2", func(c *gin.Context) {
		var login Login
		// ShouldBind()会根据请求的Content-Type自行选择绑定器
		if err := c.ShouldBind(&login); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// d. 绑定Uri示例 (/q1mi/123456)
	router.GET("/:user/:password", func(c *gin.Context) {
		var login Login
		// ShouldBindUri()会根据请求的Content-Type自行选择绑定器
		if err := c.ShouldBindUri(&login); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	router.Run(":8085")
}

// 8.1 上传单个文件
func upload() {
	r := gin.Default()
	r.LoadHTMLFiles("../../templates/upload/index.html")

	r.GET("/uploadIndex", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})

	r.POST("/upload", func(c *gin.Context) {
		// 给表单限制上传大小8M (默认 32 MiB)
		// r.MaxMultipartMemory = 8 << 20  // 8 MiB
		// 单文件上传
		f, err := c.FormFile("f1")
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			dst := path.Join("../../static/", f.Filename)
			// 上传文件到指定的路径
			c.SaveUploadedFile(f, dst)
			c.JSON(http.StatusOK, gin.H{
				"status":   "200",
				"filename": f.Filename,
			})
		}
	})

	r.Run(":80861")
}

// 8.2 上传多个文件
func upload_files() {
	r := gin.Default()
	r.LoadHTMLFiles("../../templates/upload/mul_file.html")

	r.GET("/uploadIndex", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})

	r.POST("/upload/files", func(c *gin.Context) {
		// 多文件上传
		f, err := c.MultipartForm()
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			// 获取所有文件
			files := f.File["f1"]
			// 遍历文件
			for _, file := range files {
				dst := path.Join("../../static/", file.Filename)
				// 上传文件到指定的路径
				c.SaveUploadedFile(file, dst)
			}
			c.JSON(http.StatusOK, gin.H{
				"status": "200",
			})
		}
	})

	r.Run(":80862")
}

// 9. 重定向
func redict() {
	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		c.Request.URL.Path = "/test_redict"
		r.HandleContext(c)
	})

	r.GET("/test_redict", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "test my redict!",
		})
	})

	// * 重定向到链接(http://www.baidu.com)
	r.GET("/test_redict111", func(ctx *gin.Context) {
		ctx.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")
	})

	// * 重定向到其它路由
	r.GET("/my_redict", func(ctx *gin.Context) {
		ctx.Request.URL.Path = "/test_redict"
		r.HandleContext(ctx)
	})

	r.GET("/test_redict", func(ctx *gin.Context) {
		ctx.Writer.WriteString("我是重定向返回的shuju")
	})

	r.Run(":8087")
}

func myGinView() http.Handler {
	// r := gin.Default()
	r := gin.New()
	r.Use(gin.Recovery())
	r.LoadHTMLGlob("../../templates/**/*")

	// 一个可以匹配所有请求方法的Any方法
	r.Any("/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "test my gin view success",
		})
	})

	// 为没有配置处理函数的路由添加处理程序，默认情况下它返回404代码
	r.NoRoute(func(c *gin.Context) {
		c.HTML(http.StatusNotFound, "error/404.html", nil)
	})

	return r
}

// 10. 路由组
func ginRouteGroup() http.Handler {
	// r := gin.Default()
	r := gin.New()
	r.Use(gin.Recovery())
	userGroup := r.Group("/user")
	{
		userGroup.GET("/index", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"message": "index page",
			})
		})
		userGroup.GET("/login", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"message": "login page",
			})
		})
		userGroup.GET("/logout", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"message": "logout page",
			})
		})
	}

	shopGroup := r.Group("/shop")
	{
		shopGroup.GET("/order", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"message": "order page",
			})
		})
		shopGroup.GET("/car", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"message": "car page",
			})
		})
		shopGroup.GET("/mime", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"message": "mime page",
			})
		})
	}

	return r
}

// 11. 中间件； StatCost: 统计耗时请求耗时
func StatCost() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		// a. 中间件开始执行
		fmt.Println("中间件开始执行")
		c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值，后续的处理函数能够取到该值
		c.Next()             // 执行函数, 调用该请求的剩余处理程序
		// c.Abort() 		 // 不调用该请求的剩余处理程序

		// b. 中间件执行完成后续的一些事情
		status := c.Writer.Status()
		fmt.Println("中间件执行完毕: ", status)
		var cost = time.Since(start)
		log.Panicln("耗时: ", cost)
	}
}

func middleware() http.Handler {
	r := gin.New()
	r.Use(gin.Recovery())
	// 1. 注册一个全局中间件
	r.Use(StatCost())
	{
		r.GET("/test", func(c *gin.Context) {
			// name := c.MustGet("name").(string)
			name := c.MustGet("name") // 从上下文取值
			c.JSON(http.StatusOK, gin.H{
				"message": "hello, " + name.(string),
			})
		})
		/*
			访问: localhost/test
			输出:
				"中间件开始执行"
				"message": "hello, 小王子"
				"中间件执行完毕: 200"
		*/
	}

	// 2. 为某个路由单独注册
	r.GET("/test2", StatCost(), func(c *gin.Context) {
		// name := c.MustGet("name").(string) // 从上下文取值
		name := c.MustGet("name")
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello !" + name.(string),
		})
	})

	// 3. 为路由组注册中间件
	shopGroup := r.Group("/test3", StatCost())
	{
		shopGroup.GET("/index", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"message": "group middware",
			})
		})
	}
	return r
}

func ShouldBindJson() {
	r := gin.Default()
	type UserInfo struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
		Sex  string `json:"sex"`
	}

	r.POST("/should_bind_json", func(ctx *gin.Context) {
		var userInfo UserInfo
		err := ctx.ShouldBindJSON(&userInfo)
		if err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{
				"msg": "参数错误",
			})
			return
		}
		ctx.JSON(http.StatusOK, gin.H{
			"msg": "OK",
		})
	})
	r.Run("8080")
	/**
	当请求参数为{
		"name": "cyl",
		"age": "我是一个string类型",
		"sex": "0"
	}, 则会返回{
		"msg": "参数错误"
	}，因为传递的age参数不是int类型
	*/
}

func ShouldBindQuery() {
	r := gin.Default()
	type UserInfo struct {
		Name string `json:"name" form:"name"`
		Age  int    `json:"age" form:"age"`
		Sex  string `json:"sex" form:"sex"`
	}

	r.POST("/should_bind_query", func(ctx *gin.Context) {
		var userInfo UserInfo
		err := ctx.ShouldBindQuery(&userInfo)
		if err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{
				"msg": "参数错误",
			})
			return
		}
		ctx.JSON(http.StatusOK, gin.H{
			"msg": "OK",
		})
	})
	r.Run("8080")
	/**
	当请求url为?name=cyl&age=男&sex=1, 则会返回{
		"msg": "参数错误"
	}，因为传递的age参数不是int类型
	*/
}

func ShouldBindUri() {
	r := gin.Default()
	type UserInfo struct {
		Name string `json:"name" form:"name" uri:"name"`
		Age  int    `json:"age" form:"age" uri:"age"`
		Sex  string `json:"sex" form:"sex" uri:"sex"`
	}

	r.POST("/should_bind_uri/:name/:age/:sex", func(ctx *gin.Context) {
		var userInfo UserInfo
		err := ctx.ShouldBindUri(&userInfo)
		if err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{
				"msg": "参数错误",
			})
			return
		}
		ctx.JSON(http.StatusOK, gin.H{
			"msg": "OK",
		})
	})
	r.Run("8080")
	/**
	当请求url为/cyl/男/1/, 则会返回{
		"msg": "参数错误"
	}，因为传递的age参数不是int类型
	*/
}

func ShouldBindOther() {
	r := gin.Default()
	type UserInfo struct {
		Name string `json:"name" form:"name" uri:"name"`
		Age  int    `json:"age" form:"age" uri:"age"`
		Sex  string `json:"sex" form:"sex" uri:"sex"`
	}

	r.POST("/should_bind_other", func(ctx *gin.Context) {
		var userInfo UserInfo
		err := ctx.ShouldBind(&userInfo)
		if err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{
				"msg": "参数错误",
			})
			return
		}
		ctx.JSON(http.StatusOK, gin.H{
			"msg": "OK",
		})
	})
	r.Run("8080")
	/**
	当请求url为form-data 或 www-form-urlencoded类型时，ShouldBind会自动校验content-type类型(可查看源码分析)
	*/
}

func ValidCommonUse() {
	/**
	1、常用验证器
		required: 必填/传参数: 即字段不能没有且不能为空
		min: 最小长度
		max: 最大长度
		len: 长度
		eq: 等于
		ne: 不等于
		gt: 大于
		gte: 大于等于
		lt: 小于
		lte: 小于等于
		eqfield: 等于其它字段的值, 如 Password string `binding:"eqfield=ConfirmPassword"`
		nefield: 不等于其它字段的值
		- 忽略字段(不绑定到struct上), 如 binding:"-"
	2、自定义验证错误信息
		如果使用默认的校验给出的报错不太友好，此时我们可以给struct加一个msg的tag表示自定义的error msg
	3、内置验证器
		枚举，比如 oneof=red green，表示只能是red或green
		字符串
			contains=cyl，表示包含cyl的字符串
			exclueds 不包含
			startswith 前缀
			endswith 后缀
		数组
			dive, dive后面的验证就是针对数组的每一个元素
		网络验证
			ip
			ipv4
			ipv6
			uri
			url
		日期验证: 绑定日期格式
			datetime=2016-03-04
	4、自定义验证器
		// go get -u github.com/go-playground/validator/v10
		注意这个版本得是v10的
		a. 注册验证器函数
		if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
			v.RegisterValidation("sign", signValid)
		}
		b. 编写函数: 如果用户名!=cyl就校验失败
		func signValid(fl validator.Fieldlevel) bool {
			name := fl.Field().Interface().(string)
			if name != "cyl" {
				return false
			}
			return ture
		}
		c. 使用
		type UserInfo struct {
			Name string `json:"name" binding:"sign" msg="用户名错误"`
			Age int `json:"age" binding:"min=0,max=100"`
		}
	*/
	r := gin.Default()
	type UserInfo struct {
		Name            string `json:"name" binding:"required"`
		Mobile          string `json:"mobile" binding:"required,len=11"`
		Age             int    `json:"age" binding:"gt=0,lte=120" msg:"年龄在0到120之间"`
		Password        string `json:"password"`
		ConfirmPassword string `json:"confirm_password" binding:"eqfield=Password"`
		Sex             string `json:"sex" binding:"oneof=man woman"`
		School          string `json:"school" binding:"contains=c"`
		Class           string `json:"class" binding:"startswith=上海"`
		// 绑定校验器的规则: 爱好的每个元素都必须以 球 结尾且元素个数需要>=1
		Hobby   []string `json:"hobby" binding:"required,dive,min=1,endswith=球"`
		IP      string   `json:"ip" binding:"ip"`
		BlogUrl string   `json:"blog_url" binding:"url"`
		Create  string   `json:"create" binding:"datetime=2006-01-02 15:04:05"`
	}

	r.POST("/valid/use", func(ctx *gin.Context) {
		var userInfo UserInfo
		err := ctx.ShouldBindJSON(&userInfo)
		if err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{
				"msg": "参数错误",
			})
			return
		}
		ctx.JSON(http.StatusOK, gin.H{
			"msg":  "OK",
			"data": userInfo,
		})
	})
	r.Run("8080")
}

// gin自带日志
func GinSelfLog() {
	// 日志输出到文件
	f, _ := os.Create("gin.log")
	// gin.DefaultWriter = io.MultiWriter(f)
	// 如果需要同时将日志输出到控制台并写入文件，使用以下代码
	gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
	r := gin.Default()
	r.GET("/", func(ctx *gin.Context) {
		ctx.JSON(http.StatusOK, gin.H{
			"code": 200,
		})
	})
	r.Run(":8080")
}

// https

// 安全认证
